/**
 
Uebung 5 - Stroemung
=======================

Dynamisches System - behandelt beliebige Systeme und deren zeitliche Entwicklung durch
Aenderungsraten

Alle Newton-ische Systeme gehören zu dieser Klasse

Zustand des Systems zum einem bestimmten Zeitpunkt t wird duch einen
n - wertigen reelen Vektor beschrieben = Zustandsvektor

==> 
zeitliche Entwicklung also die Aenderungsrate des Zustandsvektors wird durch ein
Vektorfeld in R^n beschrieben

Vektorfeld gibt die
	-Richtung
	-Aederungsrate des Zustandsvektors
von x(t) vor

Vektorfeld = Funktion in R^n: ordnet jedem Punkt im Gebiet einen Vekto y = f(x) zu
wobei f(x) ∈ R^n  

Beispiel x(t) ∈ R^2 ---> x = ( 	x1 )  folglich y ∈ R^2 ---> y = f(x) = ( f(x1) )
								x2										 f(x2)

Berechnung von Integralkurven: Mit Euler oder Runge-Kutta

Euler:
Integralkurve wird nährungsweise als Streckenzug ausgehend vom Startpunkt x0 berechnet:
	y = f(x)
	x' = x + y * dt
	t' = t + dt

Es verwendet die Nährung, dass der Richtungsvektor y (Geschwindigkeit) im Zeitintervall dt konstant ist

Kutta: siehe Skript

 **/
package stroemung;


import gui.DoubleProperty;
import gui.IProperty;

import javax.media.opengl.*;
import javax.media.opengl.glu.GLU;


import com.sun.opengl.util.FPSAnimator;
import com.sun.opengl.util.GLUT;

import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;


public class Stroemung extends Dynamics
       implements WindowListener, GLEventListener, MouseListener, MouseMotionListener, KeyListener
{	
	public enum Mode{_2D,_3D;}	
	
	private Mode mode = Mode._2D;
	
	private GLCanvas canvas;		//OpenGL-Canvas
	private double xmin = -0.6;
	private double xmax = 0.6;
	private DoubleProperty xminproperty = new DoubleProperty("Xmin Prop", -10, 10, 0.1, -0.6);
	private DoubleProperty xmaxproperty = new DoubleProperty("Xmax Prop", -10, 10, 0.1, 0.6);
	private double ymin	;
	private double ymax;
	//viewing volumen vergroessen, heist clipping bereich vergroessen
	double zmin = -100, zmax = 100; // Clipping Bereich
	private DoubleProperty zminproperty = new DoubleProperty("Zmin Prop", -100, 100, 1, -100);
	private DoubleProperty zmaxproperty = new DoubleProperty("Zmax Prop", -100, 100, 1,  100);
	
	
	double elev = 90; // Elevation Kamera-System
	double azim = 30; // Azimut Kamera-System
	
	private DoubleProperty elevproperty = new DoubleProperty("Elevation", -360d, 360d, 0.1d, 0d);
	private DoubleProperty azimproperty = new DoubleProperty("Azimut", -180d, 180d, 0.1d, 0d);
		
	//Zeitschritt
	private double dt = 0.002;
	
	private FPSAnimator anim;
	
	private GLUT glut = new GLUT();
	
	private boolean log = false;
	
	
	private double c_radius = 0.09;
	private double c_height = 0.30;
	
	private ArrayList<double[][]> points = new ArrayList<double[][]>();
	private double initialpos_x = -0.60;
	private double linelength = 0.002;
	private DoubleProperty linelength_property = new DoubleProperty("LinienLaenge", 0.001, 0.014, 0.001, 0.002,1000);
	private double v_space = 0.01;
	private double h_space = 0.03;
	private DoubleProperty h_space_property = new DoubleProperty("LinienAbstand", 0.001, 0.3, 0.001, 0.01,1000);
	private int linecount = 10;
	private double[][] referencepoint;
	private double colorizer = 0.2;
	
	    
	//  ------------------  Methoden  --------------------
	
	public void startAnim(){
		if (!anim.isAnimating()){
			anim.start();			
		}
	}
	
	public void stopAnim(){
		if (anim.isAnimating()){
			anim.stop();
		}
	}
	
	public void reset(){
		
	}
	
	public void modeSwitch(Mode m){
		this.mode = m;
	}
	
	public Mode getMode(){
		return this.mode;
	}
	
	public DoubleProperty[] getProperties(){
		DoubleProperty[] res = {elevproperty,azimproperty,/**xminproperty,xmaxproperty,zminproperty,zmaxproperty,h_space_property,**/
									linelength_property};
		return res;
	}
		
	public void initPoints(){
		if (!points.isEmpty()){
			points.clear();
		}
		for (int j = -20; j < linecount; ++j){
			for (int i = -3; i < 9; ++i){
				//Jede Linie beschrieben durch Start und Endpunkt
				//wird in 2x2 Matrix gespeichert
				double[][] line1 = {	{initialpos_x+linelength+2*i*h_space_property.getCurrent(), 0.1 + j * v_space},
										{initialpos_x+linelength+2*i*h_space_property.getCurrent(), 0.1 + j * v_space}
									};
				double[] p1 = {line1[0][0],line1[0][1]};
				double[] p2 = {line1[1][0],line1[1][1]};
				runge(p1, dt);
				runge(p2, dt);
				double[][] line2 = {p1,p2};
				if ((line1[0][1] > 0.015 && line1[0][1] > 0 )|| (line1[0][1] < -0.015 && line1[0][1] < 0)){
					points.add(line1);
					points.add(line2);					
				}
			}
		}
		referencepoint = points.get(0);
	}
	
	public void addPoint(){
		for (int i = -20; i < linecount; ++i){
			double[][] line1 = { {initialpos_x, 0.1 + i * v_space},
								 {initialpos_x+linelength_property.getCurrent(), 0.1 + i * v_space}									
			  				};
			if ((line1[0][0] > 0.015 && line1[0][0] > 0 )|| (line1[0][0] < -0.015 && line1[0][0] < 0)){
				points.add(line1);
			}
			referencepoint = line1;
		}
	}
			
	public void matrixRunge(){
		for (double[][] lines: points){
			runge(lines[0],dt);
			runge(lines[1],dt);
		}
	}
		
	
    public double[] f(double[] x){
        double f1 = 1 + (c_radius * c_radius) / (x[0] * x[0] + x[1] * x[1])
                        - (2 * c_radius * c_radius * x[0] * x[0]) / ( (x[0] * x[0] + x[1] * x[1]) * (x[0] * x[0] + x[1] * x[1]) );
        double f2 = - (2 * c_radius * c_radius * x[0] * x[1]) / ((x[0] * x[0] + x[1] * x[1]) * (x[0] * x[0] + x[1] * x[1]));
        double[] y = {f1,f2};
        return y;
    }


	void zeichneSLinien(GL gl){
		LinkedList<double[][]> points_scheduled_to_remove = new  LinkedList<double[][]>();
		if (mode == Mode._2D){
			//2D Ansicht
			for (double[][] lines : points){
				//Punkte sind noch sichtbar, Abbruchbedingung
				if (lines[1][0] < -initialpos_x){
//					gl.glColor3d(1.0-colorizer, 1.0-colorizer, 1.0);
					gl.glBegin(GL.GL_LINES);
					gl.glVertex3d(lines[0][0], lines[0][1], 0.14);
					gl.glVertex3d(lines[1][0], lines[1][1], 0.14);
					gl.glEnd();
				} else{
					//Punkte sind nicht mehr sichtbar - aus der Liste entfernen
					if (!lines.equals(referencepoint)){
						points_scheduled_to_remove.add(lines);					
					}
				}
			}
			if (!points_scheduled_to_remove.isEmpty()){
				points.removeAll(points_scheduled_to_remove);
			}
			
		} else{
			// 3D Ansicht
			double z = 0.075;
			for (int i = 0; i < 10; ++i){
				for (double[][] lines : points){
					//Punkte sind noch sichtbar, Abbruchbedingung
					if (lines[1][0] < -initialpos_x){
//						gl.glColor3d(1.0-colorizer, 1.0-colorizer, 1.0);
						gl.glBegin(GL.GL_LINES);
						gl.glVertex3d(lines[0][0], lines[0][1], z);
						gl.glVertex3d(lines[1][0], lines[1][1], z);
						gl.glEnd();
					} else{
						//Punkte sind nicht mehr sichtbar - aus der Liste entfernen
						if (!lines.equals(referencepoint)){
							points_scheduled_to_remove.add(lines);					
						}
					}
				}
				if (!points_scheduled_to_remove.isEmpty()){
					points.removeAll(points_scheduled_to_remove);
				}
				z+=0.04;
			}
		}
	}
	 		
	// Koordinatenachsen zeichnen
	void zeichneAchsen(GL gl) {
		gl.glBegin(GL.GL_LINES);
		// x-Achse
		gl.glVertex3d(xmin/2, 0,0);
		gl.glVertex3d(xmax/2, 0,0);
		// y-Achse
		gl.glVertex3d(0, -ymax/2,0);
		gl.glVertex3d(0, ymax/2,0);
		// z-Achse
		gl.glVertex3d(0, 0,0);
		gl.glVertex3d(0, 0,0.2);
				
		gl.glEnd();
		
	}



     // Konstruktor
     public Stroemung()
     {  
    	Frame f = new Frame("Stroemung");
        f.setSize(800, 600);
        f.addWindowListener(this);
        // OpenGL-Window
        canvas = new GLCanvas();
        canvas.addKeyListener(this);
        canvas.addGLEventListener(this);
        canvas.addMouseListener(this);
        f.add(canvas);
        f.setVisible(true);
        canvas.requestFocus();
     }
     
     public Stroemung(GLJPanel jp){

         jp.addKeyListener(this);
         jp.addGLEventListener(this);
         jp.addMouseListener(this);
         jp.setVisible(true);
         jp.requestFocus();
     }


     public static void main(String[] args)                     // main-Methode der Applikation
     {  new Stroemung();
     }


     //  ---------  OpenGL-Events  -----------------------

	public void init(GLAutoDrawable drawable) {
		//OpenGL-Objekt
		GL gl = drawable.getGL();
		gl.glClearColor(0.68f, 0.84f, 0.9f, 1.0f); // erasing color
		
		//aktivieren des z Buffers
		gl.glEnable(GL.GL_DEPTH_TEST);
		//aktivieren des Polygon Offset
		gl.glEnable(GL.GL_POLYGON_OFFSET_FILL);
		//zu den Z-Werten wir ein Offset hinzugefuegt, so das die Z Werte nicht die gleichen Werte haben
		gl.glPolygonOffset(1,1);
		//Beleuchtung aktivieren
		gl.glEnable(GL.GL_LIGHTING);
		//Wichtig: so das alle Richtungen normalisiert werden
		gl.glEnable(GL.GL_NORMALIZE);
		//Lichtquellen einschalten, positionierung in display methode
		//Aktiviert die 0 Lichtquelle
		gl.glEnable(GL.GL_LIGHT0);
		//ShadeModel einfuegen, default ist es smooth, flat ist schneller
		//bei flat hat eine flaeche, genau eine helligkeit, bei smooth werden die lichtpunkte
		//innerhalb einer flaeche noch interpoliert.
		gl.glShadeModel(GL.GL_SMOOTH);
		//diffusen koeffizienten komponenten, 3 rgb und durchsichtigkeit
		float[] werte = {0.6f, 0.6f, 0f, 1f};
		//hinten relativ dunkel -> muss streulicht erhoehen
		gl.glMaterialfv(GL.GL_FRONT, GL.GL_DIFFUSE, werte,0);
		
		anim = new FPSAnimator(drawable, 25, true);
//		anim.start();
		initPoints();
	}


	public void display(GLAutoDrawable drawable) {
		GL gl = drawable.getGL();
		gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
				
		gl.glPolygonMode(GL.GL_FRONT_AND_BACK, GL.GL_LINE);
		gl.glMatrixMode(GL.GL_MODELVIEW);
		gl.glLoadIdentity();
		// Kamera-System positionieren:
		// Kamera vom System weg bewegen, hat aber fuer die orthogonale
		// Projektion keine Wirkung, erst wenn die eleveation dazu kommt spielt
		// dies eine Rolle
		gl.glTranslated(0, 0, 0.1);
		// wichtig elevation nach dem translate;
		// notizen: ensrpicht der 1 Variante: absolute Variante
		gl.glRotated(elevproperty.getCurrent(), 1, 0, 0); // Drehung um x-Achse
		gl.glRotated(-azimproperty.getCurrent(), 0, 1, 0); // Drehung um y-Achse

		// Achsen sollen nicht beleuchtet sein
		gl.glDisable(GL.GL_LIGHTING);
		gl.glColor3d(0, 0, 0.1);

		// Hier Position der Kamera, muss die Koordinaten in einem Float Array
		// vorbereiten
		float[] pos = { 10, 10, 10, 1 };
		// Die Koordinaten des Lichts werden in das Kamerasystem umgerechnet
		// bewege ich nur die Kamera, dann haben wir eine andere Beleuchtung
		gl.glLightfv(gl.GL_LIGHT0, GL.GL_POSITION, pos, 0);
		gl.glPolygonMode(GL.GL_FRONT_AND_BACK, GL.GL_FILL);
				
		zeichneAchsen(gl);
		gl.glColor3d(0, 0, 0.8);
		glut.glutSolidCylinder(c_radius, c_height, 15, 10);
		gl.glColor3d(0, 0, 0);
		zeichneSLinien(gl);
		matrixRunge();
		if (referencepoint[1][0] >= initialpos_x + h_space){
			addPoint();
		}
	}

	//Window resized
     public void reshape(GLAutoDrawable drawable,
                         int x, int y,
                         int width, int height)
     {  GL gl = drawable.getGL();
     //damit keine Verzerrungen enstehen
     	ymax = xmaxproperty.getCurrent() * height / width;
     	ymin = -ymax;
     	gl.glMatrixMode(GL.GL_PROJECTION);
     	gl.glLoadIdentity();
        gl.glOrtho(xminproperty.getCurrent(), xmaxproperty.getCurrent(), ymin, ymax, zminproperty.getCurrent(), zmaxproperty.getCurrent());
        gl.glViewport(0, 0, width, height);
     }

     public void displayChanged(GLAutoDrawable drawable,
                                boolean modeChanged,
                                boolean deviceChanged)
     { }


     //  ---------  Window-Events  --------------------

     public void windowClosing(WindowEvent e)
     {  System.exit(0);
     }
     public void windowActivated(WindowEvent e) {  }
     public void windowClosed(WindowEvent e) {  }
     public void windowDeactivated(WindowEvent e) {  }
     public void windowDeiconified(WindowEvent e) {  }
     public void windowIconified(WindowEvent e) {  }
     public void windowOpened(WindowEvent e) {  }

 	//  ---------  Mouse Events  --------------------

 	@Override
 	public void mouseDragged(MouseEvent e) {

 	}


 	@Override
 	public void mouseMoved(MouseEvent e) {

 	}
 	
 	@Override
	public void mouseClicked(MouseEvent e) {
		// TODO Auto-generated method stub

	}

	@Override
	public void mouseEntered(MouseEvent e) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void mouseExited(MouseEvent e) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void mousePressed(MouseEvent e) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void mouseReleased(MouseEvent e) {
		// TODO Auto-generated method stub
		
	}
	
//  ---------  Key Events  --------------------

	@Override
	public void keyPressed(KeyEvent e) {
		switch (e.getKeyCode()) {
		case KeyEvent.VK_S:
				if (anim.isAnimating()) {
					anim.stop();
				} else {
					anim.start();
				}
			break;
		case KeyEvent.VK_UP:
			elev++;
			canvas.repaint();
			break;
		case KeyEvent.VK_DOWN:
			elev--;
			canvas.repaint();
			break;
		case KeyEvent.VK_LEFT:
			azim++;
			canvas.repaint();
			break;
		case KeyEvent.VK_RIGHT:
			azim--;
			canvas.repaint();
			break;
		case KeyEvent.VK_R:
			initPoints();
			break;
		case KeyEvent.VK_N:
			colorizer-=0.1;
			break;
		case KeyEvent.VK_M:
			colorizer+=0.1;
			break;
		default:
			break;
		}
	}

	@Override
	public void keyReleased(KeyEvent e) {}

	@Override
	public void keyTyped(KeyEvent e) {}
	
	private void log(String msg){
		if (log)
			System.out.println(msg);
	}
  }